﻿using System;
using System.Collections.Generic;

namespace Faml.Frontend
{
    public partial class PrecompilerContext
    {
        /// <summary>Dictionary of macro definitions</summary>
        private Dictionary<string, FamlDefinition> defs = new Dictionary<string, FamlDefinition>();
        /// <summary>Stack of macro names used in nested with operators</summary>
        private Stack<string> defNames = new Stack<string>();

        private Dictionary<string, DefinitionParameter> pars = new Dictionary<string, DefinitionParameter>();
        private Stack<string> parNames = new Stack<string>();

        private List<FamlLambdaParameter> lambdaPars = new List<FamlLambdaParameter>();
        private Stack<int> lambdaParsCount = new Stack<int>();

        /// <summary>Holds current array when processing indexer</summary>
        private Stack<FamlExpression> currentArray = new Stack<FamlExpression>();
        /// <summary>Holds current index in indexer</summary>
        private Stack<int> currentIndex = new Stack<int>();
        
        /// <summary>Supported operations</sumary>
        private OperationLibrary library;

        public PrecompilerContext(OperationLibrary operationLibrary)
        {
            this.library = operationLibrary;
        }

        public bool ContainsName(string name)
        {
            return defs.ContainsKey(name) || pars.ContainsKey(name) || LookupLambdaParameter(name) != null;
        }

        public int GetDefinitionsMarker()
        {
            return defNames.Count;
        }

        public void RemoveDefinitions(int marker)
        {
            if (defNames.Count < marker)
                throw new ArgumentException("Incorrect marker value");
            while (defNames.Count > marker)
                defs.Remove(defNames.Pop());
        }

        public int GetParametersMarker()
        {
            return parNames.Count;
        }

        public void RemoveParameters(int marker)
        {
            if (parNames.Count < marker)
                throw new ArgumentException("Incorrect marker value");
            while (parNames.Count > marker)
                pars.Remove(parNames.Pop());
        }

        public void BeginAddingLambdaParameters()
        {
            lambdaParsCount.Push(lambdaPars.Count);
        }

        public void RemoveLambdaParameters()
        {
            int count = lambdaParsCount.Pop();
            if(count > 0)
                lambdaPars.RemoveRange(count, lambdaPars.Count - count);
        }

        public void AddParameter(FamlLambdaParameter parameter)
        {
            lambdaPars.Add(parameter);
        }

        public FamlLambdaParameter LookupLambdaParameter(string name)
        {
            return lambdaPars.FindLast(p => p.Name == name);
        }

        internal FamlExpression Invoke(string ident, FamlExpression[] macroExpression)
        {
            throw new NotImplementedException();
        }

        internal int AddParameters(System.Collections.Generic.List<System.Linq.Expressions.ParameterExpression> args)
        {
            throw new NotImplementedException();
        }

      
        internal void AddLambdaExpression(string name, System.Linq.Expressions.LambdaExpression lambdaExpression)
        {
            throw new NotImplementedException();
        }

        internal void AddNamedExpression(string name, FamlExpression macroExpression)
        {
            throw new NotImplementedException();
        }

        internal FamlExpression Variable(string ident)
        {
            throw new NotImplementedException();
        }

        internal FamlExpression Indexer(System.Collections.Generic.List<FamlExpression> indices)
        {
            throw new NotImplementedException();
        }

        public OperationLibrary OperationLibrary
        {
            get
            {
                return library;
            }
        }

        public Type LookupType(string p)
        {
            switch (p)
            {
                case "int":
                    return typeof(int);
                case "double":
                    return typeof(double);
                default:
                    throw new Exception(String.Format("Unknown type {0}", p));
            }
        }

        /// <summary>Infers type from variable name (in Fortran style)</summary>
        /// <param name="name">Name of variable</param>
        /// <returns>Variable type</returns>
        public Type GetTypeFromVariableName(string name)
        {
                string[] ints = { "i", "j", "k", "l", "m", "n" };
                if (Array.IndexOf<string>(ints, name) >= 0)
                    return typeof(int);
                return typeof(double);
        }

        public void AddDefinition(FamlDefinition def)
        {
            if (defs.ContainsKey(def.Name))
                throw new InvalidOperationException("Cannot add definition with duplicate name");
            defs.Add(def.Name + "@" + def.ParameterCount.ToString(), def);
        }

        public bool ContainsDefinition(string name,int parameterCount)
        {
            string fullName = name + "@" + parameterCount.ToString();
            return defs.ContainsKey(fullName);
        }

        public FamlDefinition LookupDefinition(string name,int parameterCount)
        {
            string fullName = name + "@" + parameterCount.ToString();
            if (defs.ContainsKey(fullName))
                return defs[fullName];
            else
                return null;
        }

        public void AddParameter(DefinitionParameter par)
        {
            pars.Add(par.Name, par);
            parNames.Push(par.Name);
        }

        public DefinitionParameter LookupDefinitionParameter(string name)
        {
            if (pars.ContainsKey(name))
                return pars[name];
            else
                return null;
        }

        public void AddParameters(List<DefinitionParameter> parameters)
        {
            foreach (DefinitionParameter par in parameters)
                AddParameter(par);
        }

        public void AddParameters(List<FamlLambdaParameter> parameters)
        {
            foreach (FamlLambdaParameter par in parameters)
                AddParameter(par);
        }

        public void BeginIndexer(FamlExpression array)
        {
            currentArray.Push(array);
            currentIndex.Push(0);
        }

        public void EndIndexer()
        {
            if (currentArray.Count == 0)
                throw new InvalidOperationException("EndIndexer is called without BeginIndexer");
            currentArray.Pop();
            currentIndex.Pop();
        }

        public void NextIndex()
        {
            if (currentArray.Count == 0)
                throw new InvalidOperationException("NextIndex is called without BeginIndexer");
            currentIndex.Push(currentIndex.Pop() + 1);
        }

        public FamlExpression CurrentArray
        {
            get
            {
                if (currentArray.Count > 0)
                    return currentArray.Peek();
                else
                    return null;
            }
        }

        public int CurrentIndex
        {
            get
            {
                return currentIndex.Peek();
            }
        }

     
    }
}